﻿using System;

using System.Collections.Generic;
using System.Text;
using Silvermoon.Controls.Base;
using System.Drawing;
using Silvermoon.Utils;

namespace Silvermoon.Controls
{
    /// <summary>
    /// Navigational control that is used to display current<->back navigation as well as an optional control on the left depending on the node.
    /// see also <seealso cref="I:INavigatorNode"/>.
    /// </summary>
    public class Navigator : NavigationBase
    {
        private Tile parent, current, child;
        private Decorator optional1, optional2;
        private double scaleFactor = 2d;

        public Navigator()
            : base()
        {
            parent = new Tile { Delta = -1 };
            current = new Tile();
            child = new Tile { Delta = 1 };
            optional1 = new Decorator();
            optional2 = new Decorator();
            ControlCollection controls = VisibleControls;
            controls.Add(parent, child, current, optional1, optional2);
        }

        public double ScaleFactor
        {
            get { return scaleFactor; }
            set
            {
                if (scaleFactor != value)
                {
                    scaleFactor = value;
                    OnScaleFactorChanged();
                }
            }
        }

        protected virtual void OnScaleFactorChanged()
        {
            Invalidate(InvalidationFlag.Layout);
        }

        protected override void OnLayout()
        {
            base.OnLayout();
            if (CheckFlag(InvalidationFlag.Size))
            {
                ChangeSize();
            }
            if (CheckFlag(InvalidationFlag.Size | InvalidationFlag.LayoutAppearance))
            {
                LayoutTiles();
            }
        }

        private void ChangeSize()
        {
            int tileSize = Width / 2;
            Size size = new Size(tileSize, Height);
            parent.Size = size;
            current.Size = size;
            child.Size = size;
            size.Width = Width / 4;
            optional1.Size = size;
            optional2.Size = size;

            Point location = new Point(Width * 3 / 4, 0);
            optional1.Location = location;
            optional2.Location = location;
        }

        protected virtual void LayoutTiles()
        {
            float offset = MathUtil.FractionalDigits(this.offset);
            int tileSize = Width / 2;

            INavigationNode node = GetFocusedNode();

            if (offset <= 0f)
            {
                current.Invalidate(InvalidationFlag.Layout);
                SetExt(node, optional1);
                SetExt(node != null ? node.Next : null, optional2);
                current.Control = containerGenerator.GetContainerFromData(node);
                current.Node = node;
                parent.Control = node != null ? containerGenerator.GetContainerFromData(node.Previous) : null;
                parent.Node = node != null ? node.Previous : null;
                child.Control = node != null ? containerGenerator.GetContainerFromData(node.Next) : null;
                child.Node = node != null ? node.Previous : null;

                current.ScaleFactor = CalculateScaleFactor(offset);
                parent.ScaleFactor = CalculateScaleFactor(offset - 1d);
                child.ScaleFactor = 1f;
                current.X = (int)((Width - tileSize) / 2 + offset * tileSize * 0.75f);
                parent.X = -tileSize / 4;
                child.X = (int)(Width / 2 + (1f + offset * 2f * 0.75f) * tileSize);
                int iOpacity = (int)(255f * CalculateOpacity(offset));
                parent.Opacity = iOpacity;
                iOpacity = (int)(-255f * offset);
                child.Opacity = iOpacity;
                SetOptionalOpacity(iOpacity);
            }
            else
            {
                child.Invalidate(InvalidationFlag.Layout);
                INavigationNode prev = node != null ? node.Previous : null;
                SetExt(prev, optional1);
                SetExt(node, optional2);
                child.Control = containerGenerator.GetContainerFromData(node);
                current.Control = prev != null ? OnGetControl(prev) : null;
                prev = prev != null ? prev.Previous : null;
                parent.Control = containerGenerator.GetContainerFromData(prev);
                parent.Node = prev;

                current.ScaleFactor = CalculateScaleFactor(1f - offset);
                parent.ScaleFactor = CalculateScaleFactor(2f - offset);
                child.ScaleFactor = 1f;
                current.X = (int)((Width - tileSize) / 2 - ((1f - offset) * 0.75f) * tileSize);
                parent.X = -tileSize / 4;
                child.X = (int)(Width / 2 + (1f + ((-1f + offset) * 0.75f) * 2f) * tileSize);
                int iOpacity = (int)(255f * CalculateOpacity(1f - offset));
                parent.Opacity = iOpacity;
                iOpacity = 255 - (int)(255f * offset);
                child.Opacity = iOpacity;
                SetOptionalOpacity(iOpacity);
            }
            containerGenerator.ReleaseUnusedContainers();
        }

        private void SetOptionalOpacity(int iOpacity)
        {
            iOpacity = iOpacity * 4 / 3;

            optional1.Opacity = 255 - iOpacity;
            optional2.Opacity = iOpacity;
            optional2.Visible = iOpacity > 0;
            optional1.Visible = iOpacity < 255;
        }

        private void SetExt(INavigationNode node, Decorator opt)
        {
            IBackNavigationNode ext = node as IBackNavigationNode;
            if (ext != null)
            {
                opt.Control = ext.Optional;
                opt.Visible = containerGenerator.GetContainerFromData(ext) != null;
            }
            else
            {
                opt.Visible = false;
            }
        }

        private float CalculateOpacity(float offset)
        {
            return (float)(1d / Math.Pow(25d, Math.Abs(offset)));
        }

        private float CalculateScaleFactor(double offset)
        {
            return (float)(1d / Math.Pow(scaleFactor, Math.Abs(offset)));
        }

    }
}
